﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using GameObjects;
using GameObjects.DrawableClasses;

namespace GameProcess.Actors
{
    public class CollisionActor
    {
        readonly GameState gameState;
        List<List<HashSet<Solid>>> SolidsMap = new List<List<HashSet<Solid>>>();
        List<List<HashSet<Character>>> CharactersMap = new List<List<HashSet<Character>>>();

        public CollisionActor(GameState gameState)
        {
            this.gameState = gameState;
            gameState.CharacterAdded += new Action<GameObjects.DrawableClasses.RealCharacter>(OnCharacterAdd);
        }

        public void SetLevel(Level level)
        {
            // создаём карты зон
            int rowsCount = (int)Math.Ceiling((double)level.Height / Level.ZoneSize);
            int columnsCount = (int)Math.Ceiling((double)level.Width / Level.ZoneSize);
            SolidsMap = new List<List<HashSet<Solid>>>(columnsCount);
            CharactersMap = new List<List<HashSet<Character>>>(columnsCount);
            for (int i = 0; i < columnsCount; i++)
            {
                SolidsMap.Add(new List<HashSet<Solid>>(rowsCount));
                CharactersMap.Add(new List<HashSet<Character>>(rowsCount));
                for (int j = 0; j < rowsCount; j++)
                {
                    SolidsMap[i].Add(new HashSet<Solid>());
                    CharactersMap[i].Add(new HashSet<Character>());
                }
            }
            // добавляем каждое тело на карте в соответствующую ей зону
            foreach (var solid in level.Solids)
            {
                int x = (int)Math.Floor(solid.Position.X / Level.ZoneSize);
                int y = (int)Math.Floor(solid.Position.Y / Level.ZoneSize);
                SolidsMap[x][y].Add(solid);
            }
        }

        void OnCharacterAdd(RealCharacter character)
        {
            // добавляем нового игрока в соответствующую ему зону
            int x = (int)Math.Floor(character.Position.X / Level.ZoneSize);
            int y = (int)Math.Floor(character.Position.Y / Level.ZoneSize);
            CharactersMap[x][y].Add(character);
        }

        public void CheckForCollisions(RealCharacter character)
        {
            if (character.MoveBehavior.NewPosition == character.Position)
                return;
            // проверяем на столкновения с границами карты
            gameState.Level.CurrectIfOut(character);
            // добавляем игрока в соответствующую ему зону и удаляем с зоны, в которой он был
            int x = (int)Math.Floor(character.Position.X / Level.ZoneSize);
            int y = (int)Math.Floor(character.Position.Y / Level.ZoneSize);
            Vector2 newPos = character.MoveBehavior.NewPosition;
            int newX = (int)Math.Floor(newPos.X / Level.ZoneSize);
            int newY = (int)Math.Floor(newPos.Y / Level.ZoneSize);
            if (newX != x || newY != y)
            {
                CharactersMap[x][y].Remove(character);
                CharactersMap[newX][newY].Add(character);
            }
            // проверяем на столкновения игрока с объектами карты
            for (int i = newX - 1; i <= newX + 1; i++)
            {
                for (int j = newY - 1; j <= newY + 1; j++)
                {
                    if (i < 0 || j < 0 || i >= SolidsMap.Count || j >= SolidsMap[i].Count)
                    {
                        continue;
                    }
                    foreach (var solid in SolidsMap[i][j])
                    {
                        CorrectIfCollide(character, solid);
                    }
                }
            }
            // применяем новую позицию игрока
            character.Position = character.MoveBehavior.NewPosition;
        }

        private void CorrectIfCollide(Character character, Solid solid)
        {
            Vector2 newPos = character.MoveBehavior.NewPosition;
            Rectangle charRect = new Rectangle((int)newPos.X - (character.CollisionOffset.X >> 1), (int)newPos.Y - (character.CollisionOffset.Y >> 1),
                 character.CollisionOffset.X, character.CollisionOffset.Y);
            Rectangle solidRect = new Rectangle((int)solid.Position.X - (solid.CollisionOffset.X >> 1), (int)solid.Position.Y - (solid.CollisionOffset.Y >> 1),
                solid.CollisionOffset.X, solid.CollisionOffset.Y);
            Rectangle collisionRect = Rectangle.Intersect(charRect, solidRect);
            if (collisionRect != Rectangle.Empty)
            {
                if (collisionRect.Width < collisionRect.Height)
                {
                    if (collisionRect.X == solidRect.X)
                        newPos.X = solidRect.Left - (character.CollisionOffset.X >> 1);
                    else
                        newPos.X = solidRect.Right + (character.CollisionOffset.X >> 1);
                }
                else
                {
                    if (collisionRect.Y == solidRect.Y)
                        newPos.Y = solidRect.Top - (character.CollisionOffset.Y >> 1);
                    else
                        newPos.Y = solidRect.Bottom + (character.CollisionOffset.Y >> 1);
                }
            }
            character.MoveBehavior.NewPosition = newPos;
        }
    }
}
